package com.bluesky.framework.web.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bluesky.common.bo.LoginUser;
import com.bluesky.common.bo.SysUserBO;
import com.bluesky.common.dto.LoginDTO;
import com.bluesky.common.enums.*;
import com.bluesky.common.exception.CustomException;
import com.bluesky.common.utils.AddressUtils;
import com.bluesky.common.utils.IpUtils;
import com.bluesky.common.utils.SecurityUtils;
import com.bluesky.framework.security.config.TenantContextHolder;
import com.bluesky.framework.security.config.mobile.MobileCodeAuthenticationToken;
import com.bluesky.framework.web.service.ITokenService;
import com.bluesky.framework.web.service.IUserService;
import com.bluesky.system.common.vo.*;
import com.bluesky.system.entity.SysLog;
import com.bluesky.system.entity.SysMenu;
import com.bluesky.system.entity.SysTenant;
import com.bluesky.system.entity.SysUser;
import com.bluesky.system.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 用户实现类
 *
 * @author Kevin
 */
@Slf4j
@Service
public class UserServiceImpl implements IUserService {

    @Resource
    ITokenService tokenService;
    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private ISysMenuService sysMenuService;

    @Resource
    private ISysUserService sysUserService;

    @Resource
    private ISysUserRoleService sysUserRoleService;

    @Resource
    private ISysRoleMenuService sysRoleMenuService;

    @Resource
    private ISysTenantService sysTenantService;

    @Override
    public String login(LoginDTO req) {
        if (StrUtil.isBlank(req.getTenantCode())) {
            throw new CustomException("租户编码不能为空");
        }
        if (StrUtil.isBlank(req.getGrantType())) {
            throw new CustomException("授权类型不能为空");
        }
        if (!GrantTypeEnum.contains(req.getGrantType())) {
            throw new CustomException("授权类型暂不支持");
        }
        if (req.getGrantType().equals(GrantTypeEnum.USERNAME_PASSWORD.getCode())) {
            if (StrUtil.isBlank(req.getUsername())) {
                throw new CustomException("用户名不能为空");
            }
            if (StrUtil.isBlank(req.getPassword())) {
                throw new CustomException("密码不能为空");
            }
            return this.loginByUsername(req);
        }
        if (req.getGrantType().equals(GrantTypeEnum.MOBILE_CODE.getCode())) {
            if (StrUtil.isBlank(req.getMobile())) {
                throw new CustomException("手机号不能为空");
            }
            if (StrUtil.isBlank(req.getCode())) {
                throw new CustomException("验证码不能为空");
            }
            return this.loginByMobile(req);
        }
        return null;
    }

    @Override
    public UserInfoVO getUserInfo() {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        SysTenant sysTenant = sysTenantService.getById(loginUser.getTenantId());
        UserInfoVO userInfoVO = new UserInfoVO();
        List<RoleInfoVO> roleInfoVOList = loginUser.getSysUser().getRoles().stream().map(item -> {
            RoleInfoVO roleInfoVO = new RoleInfoVO();
            roleInfoVO.setRoleName(item.getRoleName());
            roleInfoVO.setValue(item.getRoleCode());
            return roleInfoVO;
        }).collect(Collectors.toList());
        userInfoVO.setUserId(loginUser.getSysUser().getId().toString());
        userInfoVO.setUsername(loginUser.getSysUser().getAccount());
        userInfoVO.setRealName(loginUser.getSysUser().getNickname());
        userInfoVO.setAvatar("");
        userInfoVO.setDesc("");
        userInfoVO.setRoles(roleInfoVOList);
        userInfoVO.setTenantId(loginUser.getTenantId());
        userInfoVO.setTenantCode(loginUser.getTenantCode());
        userInfoVO.setTenantTitle(sysTenant.getTenantTitle());
        userInfoVO.setTenantLogo(sysTenant.getTenantLogo());
        return userInfoVO;
    }

    @Override
    public Set<String> getPermCode() {
        return SecurityUtils.getLoginUser().getPermissions();
    }

    @Override
    public List<RouteItemVO> getMenuList() {
        List<SysMenu> sysMenuList;

        SysUserBO sysUserBO = SecurityUtils.getLoginUser().getSysUser();
        if (SecurityUtils.isSuperAdmin()) {
            LambdaQueryWrapper<SysMenu> queryWrapper = Wrappers.lambdaQuery();
            queryWrapper.eq(SysMenu::getStatus, StatusEnum.YES.getCode());
            queryWrapper.in(SysMenu::getMenuType, MenuTypeEnum.DIR.getCode(), MenuTypeEnum.MENU.getCode());
            sysMenuList = sysMenuService.list(queryWrapper);
        } else {
            sysMenuList = sysMenuService.listGrantMenuByUserId(sysUserBO.getId());
        }

        List<RouteItemVO> routeItemVOList = sysMenuList.stream()
                .filter(item -> item.getParentId().intValue() == 0)
                .sorted(Comparator.comparing(SysMenu::getSort))
                .map(item -> {
                    RouteItemVO node = new RouteItemVO();
                    node.setPath(item.getMenuType().equals(MenuTypeEnum.DIR.getCode()) ? "/" + item.getRoutePath() : item.getRoutePath());

                    node.setComponent(item.getMenuType().equals(MenuTypeEnum.DIR.getCode()) && item.getParentId().intValue() == 0 ? "LAYOUT" : item.getComponent());

                    node.setName(StrUtil.upperFirst(item.getRoutePath()));
                    node.setMeta(new RouteMetoVO());

                    RouteMetoVO routeMetoVO = new RouteMetoVO();
                    routeMetoVO.setTitle(item.getMenuName());
                    routeMetoVO.setIcon(item.getIcon());
                    if (item.getMenuType().equals(MenuTypeEnum.MENU.getCode())) {
                        routeMetoVO.setIgnoreKeepAlive(item.getKeepalive().equals(KeepaliveEnum.YES.getCode()));
                        if (item.getLinkExternal().equals(LinkExternalEnum.YES.getCode())) {
                            if (item.getFrame().equals(FrameEnum.YES.getCode())) {
                                routeMetoVO.setFrameSrc(item.getLinkUrl());
                            }
                            if (item.getFrame().equals(FrameEnum.NO.getCode())) {
                                node.setPath(item.getLinkUrl());
                            }
                        }
                    }
                    node.setMeta(routeMetoVO);
                    node.setChildren(getChildrenList(item, sysMenuList));
                    return node;
                }).collect(Collectors.toList());
        return routeItemVOList;
    }

    @Override
    public AccountInfoVO getAccountInfo() {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        SysUser sysUser = sysUserService.getById(loginUser.getSysUser().getId());
        return BeanUtil.copyProperties(sysUser, AccountInfoVO.class);
    }

    @Override
    public void saveAccountInfo(AccountInfoVO req) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        SysUser entity = BeanUtil.copyProperties(req, SysUser.class);
        entity.setId(loginUser.getSysUser().getId());
        sysUserService.updateById(entity);
    }

    private List<RouteItemVO> getChildrenList(SysMenu root, List<SysMenu> list) {
        List<RouteItemVO> childrenList = list.stream()
                .filter(item -> item.getParentId().equals(root.getId()))
                .sorted(Comparator.comparing(SysMenu::getSort))
                .map(item -> {
                    RouteItemVO node = new RouteItemVO();
                    node.setPath(item.getMenuType().equals(MenuTypeEnum.DIR.getCode()) ? "/" + item.getRoutePath() : item.getRoutePath());
                    node.setComponent(item.getMenuType().equals(MenuTypeEnum.DIR.getCode()) && item.getParentId().intValue() == 0 ? "LAYOUT" : item.getComponent());
                    node.setName(StrUtil.upperFirst(item.getRoutePath()));
                    node.setMeta(new RouteMetoVO());

                    RouteMetoVO routeMetoVO = new RouteMetoVO();
                    routeMetoVO.setTitle(item.getMenuName());
                    routeMetoVO.setIcon(item.getIcon());
                    if (item.getMenuType().equals(MenuTypeEnum.MENU.getCode())) {
                        routeMetoVO.setIgnoreKeepAlive(item.getKeepalive().equals(KeepaliveEnum.YES.getCode()));
                        if (item.getLinkExternal().equals(LinkExternalEnum.YES.getCode())) {
                            if (item.getFrame().equals(FrameEnum.YES.getCode())) {
                                routeMetoVO.setFrameSrc(item.getLinkUrl());
                            }
                            if (item.getFrame().equals(FrameEnum.NO.getCode())) {
                                node.setPath(item.getLinkUrl());
                            }
                        }
                    }
                    node.setMeta(routeMetoVO);
                    node.setChildren(getChildrenList(item, list));
                    return node;
                }).collect(Collectors.toList());
        return childrenList;
    }

    private String loginByUsername(LoginDTO req) {
        // 用户验证，调用loadUserByUsername
        Authentication authentication = null;
        try {
            authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(req.getUsername(), req.getPassword()));
        } catch (AuthenticationException e) {
            recordLoginLog(LogStatusEnum.FAILURE.getCode(), req, req.getUsername(), e.getMessage());
            throw new CustomException(e.getMessage());
        }
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        log.info("登录用户：{}", req.getUsername());
        recordLoginLog(LogStatusEnum.SUCCESS.getCode(), req, req.getUsername(), null);
        return tokenService.createToken(loginUser);
    }

    private String loginByMobile(LoginDTO req) {
        // 用户验证，调用loadUserByMobile
        Authentication authentication = null;
        try {
            authentication = authenticationManager.authenticate(new MobileCodeAuthenticationToken(req.getMobile(), req.getCode()));
        } catch (AuthenticationException e) {
            recordLoginLog(LogStatusEnum.FAILURE.getCode(), req, req.getMobile(), e.getMessage());
            throw new CustomException(e.getMessage());
        }
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        log.info("登录用户：{}", req.getMobile());
        recordLoginLog(LogStatusEnum.SUCCESS.getCode(), req, req.getMobile(), null);
        return tokenService.createToken(loginUser);
    }

    public void recordLoginLog(String logStatus, LoginDTO req, String username, String exceptionMessage) {
        SysTenant sysTenant = sysTenantService.getOne(Wrappers.lambdaQuery(SysTenant.class).eq(SysTenant::getTenantCode, TenantContextHolder.getCurrentTenant()));
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        SysLog sysLog = new SysLog();
        sysLog.setTenantId(sysTenant.getId());
        sysLog.setTitle("登录系统");
        sysLog.setLogStatus(LogStatusEnum.SUCCESS.getCode());
        sysLog.setUserPlatform(UserPlatformEnum.WEB.getCode());
        sysLog.setRequsetUri(request.getRequestURI());
        sysLog.setRequsetType(request.getMethod());
        sysLog.setRequsetMethod(null);

        sysLog.setRequsetParams(JSONUtil.toJsonStr(req));
        sysLog.setResponseResult(null);
        sysLog.setRequsetTime(null);

        String ipAddress = IpUtils.getClientIP(request);
        sysLog.setIpAddress(ipAddress);
        sysLog.setOperLocation(AddressUtils.getRealAddressByIp(ipAddress));
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
        sysLog.setBrowser(userAgent.getBrowser().getName());
        sysLog.setOs(userAgent.getOs().getName());

        sysLog.setOperName(username);

        sysLog.setLogStatus(logStatus);
        sysLog.setException(exceptionMessage);

        ThreadUtil.execAsync(() -> {
            ISysLogService sysLogService = SpringUtil.getBean(ISysLogService.class);
            sysLogService.save(sysLog);
        });
    }

}
